package io.hepu.robotize.http;

import io.hepu.robotize.util.Limits;
import org.apache.http.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Function;

@Component
public class HttpService implements IHttpService<HttpEntity> {
    private final static int DEFAULT_BUFFER_LENGTH = 4 * 1024;
    private final static Charset DEFAULT_CHARSET = Consts.UTF_8;

    @Resource
    private CloseableHttpClient httpClient;

    // 静态状态码处理映射表
    private static final Map<Integer, Function<HttpEntity, HttpResult<HttpEntity>>> HANDLERS = new HashMap<>();

    static {
        HANDLERS.put(HttpStatus.SC_OK, HttpResult::ok);
        HANDLERS.put(HttpStatus.SC_NO_CONTENT, HttpResult::ok);
        HANDLERS.put(HttpStatus.SC_BAD_REQUEST, HttpResult::fail);
        HANDLERS.put(HttpStatus.SC_UNAUTHORIZED, HttpResult::unauthorized);
        HANDLERS.put(HttpStatus.SC_FORBIDDEN, HttpResult::forbidden);
        HANDLERS.put(HttpStatus.SC_NOT_FOUND, HttpResult::notFound);
        HANDLERS.put(HttpStatus.SC_INTERNAL_SERVER_ERROR, HttpResult::internalError);
        HANDLERS.put(HttpStatus.SC_SERVICE_UNAVAILABLE, HttpResult::unavailable);
        HANDLERS.put(HttpStatus.SC_CONFLICT, HttpResult::conflict);
    }

    @Override
    public HttpResult<HttpEntity> doGet(String url) throws Exception {
        return this.doGet(url, Collections.emptyMap(), Collections.emptyMap());
    }

    @Override
    public HttpResult<HttpEntity> doGet(String url, Map<String, Object> parameters) throws Exception {
        return this.doGet(url, Collections.emptyMap(), parameters);
    }

    @Override
    public HttpResult<HttpEntity> doPost(String url) throws Exception {
        return this.doPost(url, Collections.emptyMap(), Collections.emptyMap());
    }

    @Override
    public HttpResult<HttpEntity> doPost(String url, Map<String, Object> parameters) throws Exception {
        return this.doPost(url, Collections.emptyMap(), parameters);
    }

    @Override
    public HttpResult<HttpEntity> doPost(String url, String jsonParameters) throws Exception {
        return this.doPost(url, Collections.emptyMap(), jsonParameters);
    }

    /**
     * Get请求
     */
    @Override
    public HttpResult<HttpEntity> doGet(String url, Map<String, Object> headers, Map<String, Object> parameters) throws Exception {
        URIBuilder uriBuilder = new URIBuilder(url);
        if (Limits.nonEmpty(parameters)) {
            parameters.forEach((key, value) -> uriBuilder.setParameter(key, value.toString()));
        }
        HttpGet httpGet = new HttpGet(uriBuilder.build().toString());
        setHeaders(httpGet, headers);
        try (CloseableHttpResponse response = this.httpClient.execute(httpGet)) {
            return getHttpResult(response);
        }
    }

    /**
     * Post请求：表单形式的参数
     */
    @Override
    public HttpResult<HttpEntity> doPost(String url, Map<String, Object> headers, Map<String, Object> parameters) throws Exception {
        HttpPost httpPost = new HttpPost(url);
        setHeaders(httpPost, headers);
        if (Limits.nonEmpty(parameters)) {
            List<NameValuePair> list = new ArrayList<>();
            parameters.forEach((key, value) -> list.add(new BasicNameValuePair(key, value.toString())));
            UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(list, DEFAULT_CHARSET);
            httpPost.setEntity(urlEncodedFormEntity);
        }
        try (CloseableHttpResponse response = this.httpClient.execute(httpPost)) {
            return getHttpResult(response);
        }
    }

    /**
     * Post请求：Json形式的参数
     */
    @Override
    public HttpResult<HttpEntity> doPost(String url, Map<String, Object> headers, String jsonParameters) throws Exception {
        HttpPost httpPost = new HttpPost(url);
        setHeaders(httpPost, headers);
        httpPost.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
        httpPost.setEntity(new StringEntity(jsonParameters, ContentType.APPLICATION_JSON.withCharset(DEFAULT_CHARSET)));
        return getHttpResult(this.httpClient.execute(httpPost));
    }

    /**
     * 公共方法：设置请求头
     */
    private void setHeaders(HttpRequestBase httpRequest, Map<String, Object> headers) {
        if (Limits.nonEmpty(headers)) {
            headers.forEach((key, value) -> httpRequest.addHeader(key, value.toString()));
        }
    }


    /**
     * 处理HttpResponse，返回HttpResult
     */
    public static HttpResult<HttpEntity> getHttpResult(CloseableHttpResponse response) throws IOException {
        if (response == null || response.getStatusLine() == null) {
            return null;
        }
        int statusCode = response.getStatusLine().getStatusCode();
        HttpEntity entity = response.getEntity();
        // 如果实体为空，返回空实体的结果
        if (entity == null) {
            return HttpResult.of(statusCode, null);
        }
        // 根据状态码调用对应的处理方法
        // body 是通过 handler.apply(entity) 传递的，entity 就是从 response.getEntity() 得到的 HttpEntity 对象.
        // body 是 Lambda 表达式中的参数名称，在 apply 方法调用时，它接受传递过来的 entity.
        Function<HttpEntity, HttpResult<HttpEntity>> handler = HANDLERS.getOrDefault(statusCode, body -> HttpResult.of(statusCode, body));
        return handler.apply(entity);
    }

    public String getHttpResult(HttpEntity entity) throws IOException {
        try (InputStream inputStream = entity.getContent()) {
            ByteArrayOutputStream buffer = new ByteArrayOutputStream(DEFAULT_BUFFER_LENGTH);
            byte[] tempBuffer = new byte[DEFAULT_BUFFER_LENGTH];
            int bytesRead;
            while ((bytesRead = inputStream.read(tempBuffer)) != -1) {
                buffer.write(tempBuffer, 0, bytesRead);
            }
            return buffer.toString(DEFAULT_CHARSET.name());
        }
    }

}
